/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.thienloc.omap.controller;

import com.thienloc.omap.entity.BroadcastProduction;
import com.thienloc.omap.entity.SmsKeywordServiceMapping;
import com.thienloc.omap.entity.SyntaxDefinition;
import com.thienloc.omap.entity.report.BroadcastProductionOfSyntax;
import com.thienloc.omap.entity.report.BroadcastProductionOfUser;
import com.thienloc.omap.jdbc.dao.TblSmsBroadcastingDao;
import com.thienloc.omap.session.BroadcastProductionFacade;
import com.thienloc.omap.session.RequestQueueFacade;
import com.thienloc.omap.session.RequestQueueHistFacade;
import com.thienloc.omap.session.SmsKeywordServiceMappingFacade;
import com.thienloc.omap.session.SyntaxDefinitionFacade;
import com.thienloc.omap.util.Constant;
import com.thienloc.omap.util.JsfUtil;
import java.io.FileReader;
import java.io.Serializable;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author GiangLT
 */
@ManagedBean(eager = true)
@RequestScoped
public class CountProductionBean implements Serializable {
    
    @EJB
    private SyntaxDefinitionFacade syntaxDefinitionFacade;
    @EJB
    private SmsKeywordServiceMappingFacade keywordServiceMappingFacade;
    @EJB
    private RequestQueueFacade requestQueueFacade;
    @EJB
    private RequestQueueHistFacade requestQueueHistFacade;
    @EJB
    private BroadcastProductionFacade broadcastProductionFacade;
    private final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
    
    @PostConstruct
    public void init() {
        try {
            Calendar calForCountBroadcast = Calendar.getInstance();
            Date day = calForCountBroadcast.getTime();
            String month = getMonthOrDayString(calForCountBroadcast.get(Calendar.MONTH) + 1);
            String year = String.valueOf(calForCountBroadcast.get(Calendar.YEAR));
            
            List<Runnable> countTasks = new ArrayList<Runnable>();
            countTasks.add(new CountProductionOfBroadcast(month, year, "smsgate_viettel"));
            countTasks.add(new CountProductionOfBroadcast(month, year, "smsgate_mobifone"));
            countTasks.add(new CountProductionOfBroadcast(month, year, "smsgate_vinaphone"));
            
            countTasks.add(new CountProductionOfBroadcast(day, "smsgate_viettel"));
            countTasks.add(new CountProductionOfBroadcast(day, "smsgate_mobifone"));
            countTasks.add(new CountProductionOfBroadcast(day, "smsgate_vinaphone"));
            
            Calendar startOfToday = Calendar.getInstance();
            startOfToday.set(Calendar.HOUR, 0);
            startOfToday.set(Calendar.MINUTE, 0);
            startOfToday.set(Calendar.SECOND, 0);
            Calendar endOfToday = Calendar.getInstance();
            endOfToday.set(Calendar.HOUR, 23);
            endOfToday.set(Calendar.MINUTE, 59);
            endOfToday.set(Calendar.SECOND, 59);
            countTasks.add(new CountProductionOfOperator(startOfToday.getTime(), endOfToday.getTime(), "smsgate_viettel"));
            countTasks.add(new CountProductionOfOperator(startOfToday.getTime(), endOfToday.getTime(), "smsgate_mobifone"));
            countTasks.add(new CountProductionOfOperator(startOfToday.getTime(), endOfToday.getTime(), "smsgate_vinaphone"));
            
            Calendar startOfThisMonth = Calendar.getInstance();
            startOfThisMonth.set(Calendar.DATE, 1);
            startOfThisMonth.set(Calendar.HOUR, 0);
            startOfThisMonth.set(Calendar.MINUTE, 0);
            startOfThisMonth.set(Calendar.SECOND, 0);
            Calendar endOfThisMonth = Calendar.getInstance();
            endOfThisMonth.set(Calendar.DATE, endOfThisMonth.getActualMaximum(Calendar.DATE));
            endOfThisMonth.set(Calendar.HOUR, 23);
            endOfThisMonth.set(Calendar.MINUTE, 59);
            endOfThisMonth.set(Calendar.SECOND, 59);
            countTasks.add(new CountProductionOfOperator(startOfThisMonth.getTime(), endOfThisMonth.getTime(), "smsgate_viettel"));
            countTasks.add(new CountProductionOfOperator(startOfThisMonth.getTime(), endOfThisMonth.getTime(), "smsgate_mobifone"));
            countTasks.add(new CountProductionOfOperator(startOfThisMonth.getTime(), endOfThisMonth.getTime(), "smsgate_vinaphone"));
            
            Properties properties = new Properties();
            properties.load(new FileReader(Constant.CONFIG_FILE_PATH));
            String refDatePattern = properties.getProperty("countProduction.refDatePattern");
            String refTimeStr = properties.getProperty("countProduction.referenceTime");
            DateFormat dateFormat = new SimpleDateFormat(refDatePattern);
            Calendar refTime = Calendar.getInstance();
            refTime.setTime(dateFormat.parse(refTimeStr));
            
            TimerTask timerTask = new TimeChecker(dateFormat, refTime, countTasks);
            Timer timer = new Timer(true);
            timer.scheduleAtFixedRate(timerTask, 1L, Long.parseLong(properties.getProperty("countProduction.countTimerPeroid")));
        } catch (Exception ex) {
//            logger.error("", ex);
            ex.printStackTrace();
        }
    }
    
    public class TimeChecker extends TimerTask {
        
        private final Logger logger = LoggerFactory.getLogger(TimeChecker.class);
        private DateFormat referenceDateFormat;
        private Calendar referenceTime;
        private ExecutorService executor;
        private List<Runnable> tasks;
        
        public TimeChecker(DateFormat referenceDateFormat, Calendar referenceTime, ExecutorService executor, List<Runnable> tasks) {
            this.referenceDateFormat = referenceDateFormat;
            this.referenceTime = referenceTime;
            this.executor = executor;
            this.tasks = tasks;
        }
        
        public TimeChecker(DateFormat referenceDateFormat, Calendar referenceTime, List<Runnable> tasks) {
            this.referenceDateFormat = referenceDateFormat;
            this.referenceTime = referenceTime;
            this.tasks = tasks;
        }
        
        @Override
        public void run() {
            logger.trace("Running TimeChecker");
            try {
                Calendar currentTime = Calendar.getInstance();
                String refTimeString = referenceDateFormat.format(referenceTime.getTime());
                String curTimeString = referenceDateFormat.format(currentTime.getTime());
                if (refTimeString.equals(curTimeString)) {
                    logger.debug("Current time ({}) matched reference time. Executing all count tasks", curTimeString);
                    broadcastProductionFacade.deleteAll();
                    if (executor == null || executor.isShutdown()) {
                        executor = Executors.newFixedThreadPool(tasks.size());
                    }
                    
                    for (Runnable task : tasks) {
                        executor.execute(task);
                    }
                }
            } catch (Exception ex) {
                logger.error("", ex);
            }
        }
    }
    
    private class CountProductionOfBroadcast implements Runnable {
        
        private Date day;
        private String month;
        private String year;
        private String provider;
        private final Logger logger = LoggerFactory.getLogger(CountProductionOfBroadcast.class);
        
        public CountProductionOfBroadcast(Date day, String provider) {
            this.day = day;
            this.provider = provider;
        }
        
        public CountProductionOfBroadcast(String month, String year, String provider) {
            this.month = month;
            this.year = year;
            this.provider = provider;
        }
        
        @Override
        public void run() {
            try {
                
                Map<String, Integer> unmatchSmses = new HashMap<String, Integer>();
                Map<String, BroadcastProductionOfUser> userMap = createDefaultUsermap();

                //lấy toàn bộ các sms đã shot
                TblSmsBroadcastingDao smsBroadcastingDao = new TblSmsBroadcastingDao();
                Map<String, Integer> smsBroadcastingMap;
                List<String> providers = new ArrayList<String>(1);
                providers.add(provider);
                
                if (day == null) {
                    logger.trace("Counting all broadcasted messages in {}/{}", month, year);
                    smsBroadcastingMap = smsBroadcastingDao.find(month, year, providers);
                } else {
                    String dayStr = getMonthOrDayString(day.getDate());
                    month = getMonthOrDayString(day.getMonth() + 1);
                    year = String.valueOf(1900 + day.getYear());
                    
                    logger.trace("Counting all broadcasted messages in {}/{}/{}", new Object[]{dayStr, month, year});
                    smsBroadcastingMap = smsBroadcastingDao.find(dayStr, month, year, providers);
                }
                logger.trace("Total {} unique broadcasted messages", smsBroadcastingMap.size());
                
                Set<String> broadcastedSmses = smsBroadcastingMap.keySet();
                List<SmsKeywordServiceMapping> keywordServiceMappings = keywordServiceMappingFacade.findAll();
                
                
                for (String broadcastedSms : broadcastedSmses) {
                    
                    Integer broadcastedSmsProduction = smsBroadcastingMap.get(broadcastedSms);
                    boolean matchFound = false;
                    for (SmsKeywordServiceMapping smsKeywordServiceMapping : keywordServiceMappings) {
                        
                        if (broadcastedSms.toUpperCase().contains(smsKeywordServiceMapping.getContentKeyword().toUpperCase())) {
                            String syntax = smsKeywordServiceMapping.getSyntaxPrefix();
                            List<SyntaxDefinition> syntaxDefinitions = syntaxDefinitionFacade.findBySyntax(syntax);
                            if (syntaxDefinitions.size() == 1
                                    && syntaxDefinitions.get(0).getMdnSuffixEndRange() == -1
                                    && syntaxDefinitions.get(0).getMdnSuffixStartRange() == -1) {
                                //=> cú pháp này chỉ có 1 người sử dụng
                                String username = syntaxDefinitions.get(0).getUsername();
                                BroadcastProductionOfUser broadcastProductionOfUser = userMap.get(username);
                                broadcastProductionOfUser.increaseProduction(syntax, broadcastedSmsProduction);
                                broadcastProductionOfUser.addMessageToSyntax(syntax, broadcastedSms);
                            }
                            
                            matchFound = true;
                            break;
                        }
                        
                    }
                    if (!matchFound) {
                        logger.debug("No match found ({}):  {}", broadcastedSmsProduction, broadcastedSms);
                        unmatchSmses.put(broadcastedSms, broadcastedSmsProduction);
                    }
                }


                //DB insert
                Date startTime;
                Date endTime;
                
                if (day == null) {
                    Calendar startOfThisMonth = Calendar.getInstance();
                    startOfThisMonth.set(Calendar.YEAR, Integer.parseInt(year));
                    startOfThisMonth.set(Calendar.MONTH, Integer.parseInt(month) + 1);
                    startOfThisMonth.set(Calendar.DATE, 1);
                    startOfThisMonth.set(Calendar.HOUR, 0);
                    startOfThisMonth.set(Calendar.MINUTE, 0);
                    startOfThisMonth.set(Calendar.SECOND, 0);
                    startTime = startOfThisMonth.getTime();
                    
                    Calendar endOfThisMonth = Calendar.getInstance();
                    endOfThisMonth.set(Calendar.YEAR, Integer.parseInt(year));
                    endOfThisMonth.set(Calendar.MONTH, Integer.parseInt(month) + 1);
                    endOfThisMonth.set(Calendar.DATE, endOfThisMonth.getActualMaximum(Calendar.DATE));
                    endOfThisMonth.set(Calendar.HOUR, 23);
                    endOfThisMonth.set(Calendar.MINUTE, 59);
                    endOfThisMonth.set(Calendar.SECOND, 59);
                    endTime = endOfThisMonth.getTime();
                    
                } else {
                    Calendar startOfToday = Calendar.getInstance();
                    startOfToday.setTime(day);
                    startOfToday.set(Calendar.HOUR, 0);
                    startOfToday.set(Calendar.MINUTE, 0);
                    startOfToday.set(Calendar.SECOND, 0);
                    startTime = startOfToday.getTime();
                    
                    Calendar endOfToday = Calendar.getInstance();
                    endOfToday.setTime(day);
                    endOfToday.set(Calendar.HOUR, 23);
                    endOfToday.set(Calendar.MINUTE, 59);
                    endOfToday.set(Calendar.SECOND, 59);
                    endTime = endOfToday.getTime();
                }
                
                List<BroadcastProductionOfUser> list = new ArrayList<BroadcastProductionOfUser>(userMap.values());
                for (BroadcastProductionOfUser broadcastProductionOfUser : list) {
                    String username = broadcastProductionOfUser.getUsername();
                    for (BroadcastProductionOfSyntax broadcastProductionOfSyntax : broadcastProductionOfUser.getProductionOfSyntax()) {
                        String syntax = broadcastProductionOfSyntax.getSyntax();
                        Integer broadcast = broadcastProductionOfSyntax.getProduction();
                        broadcastProductionFacade.upsertBroadcastAmount(username, syntax, provider, broadcast, startTime, endTime);
                    }
                }
                
                for (Entry<String, Integer> unmatchSmsEntry : unmatchSmses.entrySet()) {
                    String smsContent = unmatchSmsEntry.getKey();
                    Integer production = unmatchSmsEntry.getValue();
                    
                    BroadcastProduction broadcastProduction = new BroadcastProduction();
                    broadcastProduction.setEndTime(endTime);
                    broadcastProduction.setProduction(BigInteger.valueOf(production));
                    broadcastProduction.setProvider(provider);
                    broadcastProduction.setStartTime(startTime);
                    broadcastProduction.setUnparsableSms(smsContent);
                    broadcastProduction.setUpdatedTime(new Date());
                    
                    broadcastProductionFacade.create(broadcastProduction);
                }
            } catch (Exception ex) {
                logger.error("", ex);
            }
        }
    }
    
    private class CountProductionOfOperator implements Runnable {
        
        private Date startTime;
        private Date endTime;
        private String provider;
        private final Logger logger = LoggerFactory.getLogger(CountProductionOfOperator.class);
        
        public CountProductionOfOperator(Date startTime, Date endTime, String provider) {
            this.startTime = startTime;
            this.endTime = endTime;
            this.provider = provider;
        }
        
        @Override
        public void run() {
            try {
                Map<String, Integer> unparsableRequests = new HashMap<String, Integer>();
                Map<String, BroadcastProductionOfUser> userMap = createDefaultUsermap();
                List<String> providers = new ArrayList<String>(1);
                providers.add(provider);
                
                logger.trace("Counting all received smses (MO)");
                Map<String, Integer> requestDetailMap = requestQueueFacade.countProductionOfOperators(startTime, endTime, "6771", providers);
                Map<String, Integer> requestDetailHistMap = (requestQueueHistFacade.countProductionOfOperators(startTime, endTime, "6771", providers));
                for (String requestDetailHist : requestDetailHistMap.keySet()) {
                    if (requestDetailMap.containsKey(requestDetailHist)) {
                        requestDetailMap.put(requestDetailHist, requestDetailHistMap.get(requestDetailHist) + requestDetailMap.get(requestDetailHist));
                    } else {
                        requestDetailMap.put(requestDetailHist, requestDetailHistMap.get(requestDetailHist));
                    }
                }
                logger.trace("Total {} broadcasted messages", requestDetailMap.size());
                
                Set<String> requestDetails = requestDetailMap.keySet();
                for (String requestDetail : requestDetails) {
                    
                    if (requestDetail != null && !requestDetail.trim().isEmpty()) {
                        //tách lấy cú pháp (từ đầu tiên)
                        String syntax;
                        if (requestDetail.startsWith("X2")) {
                            syntax = "X2";
                        } else {
                            syntax = requestDetail.split(" ")[0].trim();
                        }
                        
                        List<SyntaxDefinition> syntaxDefinitions;
                        try {
                            syntaxDefinitions = syntaxDefinitionFacade.findBySyntax(syntax);
                            if (syntaxDefinitions.isEmpty()) {
                                //=> cú pháp ko tồn tại
                                logger.warn("Unparsable request detail: ({}) {}", requestDetailMap.get(requestDetail), requestDetail);
                                unparsableRequests.put(requestDetail, requestDetailMap.get(requestDetail));
                            } else if (syntaxDefinitions.size() == 1
                                    && syntaxDefinitions.get(0).getMdnSuffixEndRange() == -1
                                    && syntaxDefinitions.get(0).getMdnSuffixStartRange() == -1) {
                                //=> cú pháp này chỉ có 1 người sử dụng
                                String username = syntaxDefinitions.get(0).getUsername();
                                BroadcastProductionOfUser broadcastProductionOfUser = userMap.get(username);
                                broadcastProductionOfUser.increaseProduction(syntax, requestDetailMap.get(requestDetail));
                                broadcastProductionOfUser.addMessageToSyntax(syntax, requestDetail);
                                broadcastProductionOfUser.setUniqueOperator(true);
                                //                        logger.debug("[{}]: Found {} {} - {}", new Object[]{getUsername(), username, requestDetail, requestDetailMap.get(requestDetail)});
                                //                        logger.debug("[{}]: {}", new Object[]{getUsername(), broadcastProductionOfUser.toString()});

                            }
                        } catch (Exception ex) {
                            logger.error("", ex);
                        }
                    }
                }
                
                
                //DB insert
                List<BroadcastProductionOfUser> list = new ArrayList<BroadcastProductionOfUser>(userMap.values());
                for (BroadcastProductionOfUser broadcastProductionOfUser : list) {
                    String username = broadcastProductionOfUser.getUsername();
                    for (BroadcastProductionOfSyntax broadcastProductionOfSyntax : broadcastProductionOfUser.getProductionOfSyntax()) {
                        String syntax = broadcastProductionOfSyntax.getSyntax();
                        Integer production = broadcastProductionOfSyntax.getProduction();
                        
                        broadcastProductionFacade.upsertProduction(username, syntax, provider, production, startTime, endTime);
                    }
                }
                
                for (Entry<String, Integer> unmatchSmsEntry : unparsableRequests.entrySet()) {
                    String smsContent = unmatchSmsEntry.getKey();
                    Integer production = unmatchSmsEntry.getValue();
                    
                    BroadcastProduction broadcastProduction = new BroadcastProduction();
                    broadcastProduction.setEndTime(endTime);
                    broadcastProduction.setProduction(BigInteger.valueOf(production));
                    broadcastProduction.setProvider(provider);
                    broadcastProduction.setStartTime(startTime);
                    broadcastProduction.setUnparsableSms(smsContent);
                    broadcastProduction.setUpdatedTime(new Date());
                    
                    broadcastProductionFacade.create(broadcastProduction);
                }
                
                
            } catch (Exception ex) {
                logger.error("", ex);
            }
        }
    }
    
    private String getMonthOrDayString(int monthOrDay) {
        if (monthOrDay < 10) {
            return "0" + String.valueOf(monthOrDay);
        } else {
            return String.valueOf(monthOrDay);
        }
    }
    
    private Map<String, BroadcastProductionOfUser> createDefaultUsermap() throws Exception {
        List<String> operatorUsers = syntaxDefinitionFacade.findAllUsername();
        Map<String, BroadcastProductionOfUser> userMap = new HashMap<String, BroadcastProductionOfUser>(operatorUsers.size());

        //tạo map default
        for (String username : operatorUsers) {
            BroadcastProductionOfUser broadcastProductionOfUser = new BroadcastProductionOfUser();
            broadcastProductionOfUser.setUsername(username);
            
            List<SyntaxDefinition> managingSyntaxes;
            try {
                managingSyntaxes = syntaxDefinitionFacade.findByUsername(username);
                for (SyntaxDefinition managingSyntax : managingSyntaxes) {
                    BroadcastProductionOfSyntax bpos = new BroadcastProductionOfSyntax(managingSyntax.getSyntax(), 0); //default = 0
                    bpos.setMdnSuffixEndRange(managingSyntax.getMdnSuffixEndRange());
                    bpos.setMdnSuffixStartRange(managingSyntax.getMdnSuffixStartRange());
                    broadcastProductionOfUser.addSyntax(bpos);
                }
                userMap.put(username, broadcastProductionOfUser);
            } catch (Exception ex) {
                logger.error("", ex);
                JsfUtil.addErrorMessage("Lỗi", ex.getMessage());
            }
        }
        
        return userMap;
    }
}
